/**
 * Drag n' Drop
 * A instanciacao inicializa as propriedades de nao selecao no objeto e tambem cria os tipos de drag de acordo com os parametros
 * @param {Node, Array} No' ou Colecao de No's que serao a base do drag
 * @param {Node, Array} No' ou Colecao de No's que serao os targets do drag
 * @param {String} Tipo de colisao (boudingBox, edgeBox)
 */
function DD(base,targets,collisionType){
	
	this.base=base;
	
	if(DD.collision.type.hasOwnProperty(collisionType)){
		this.collisionType=collisionType;
	}

	this.targets = targets || base;
	this.hitTest = this.targets instanceof Array ? DD.collision.manyTargets : DD.collision.oneTarget;
	this.hitTest.type = DD.collision.type[this.collisionType];
	
	
	if(base instanceof Array){
		for(var i=0,b;b=base[i];i++){
			b.DD = this;
			b.unselectable="on";
			b.style.MozUserSelect="none";
			b.style.KhtmlUserSelect="none";
			compono.Event.add("mousedown",this.start,b);
		}
	}else{
		base.DD = this;
		base.unselectable="on";
		base.style.MozUserSelect="none";
		base.style.KhtmlUserSelect="none";
		compono.Event.add("mousedown",this.start,base);
	}
}


DD.prototype = {
	/**
	 * No' base ou colecao de No's
	 */
	base:false,
	/**
	 * Cache de cordenadas do mouse relativo ao objeto
	 */
	gap:{x:0,y:0},
	/**
	 * Estados iniciais do objeto para posterior atribuicao
	 * estado onde o objeto deve ficar durante o drag
	 */
	state:{
		init:{p:'',x:0,y:0,z:0},
		next:{p:'absolute',z:1000}
	},
	/**
	 * No' ou colecao de no's que serao os targes do drag
	 */
	targets:[],
	/**
	 * Colecao que guarda as colisoes ocorridas durante o movimento do drag
	 */
	colliders:[],
	/**
	 * Tipo de colisao default
	 */
	collisionType:"edgeBox",
	/**
	 * Funcao que testa a colisao de acordo com a quantidade de elementos TARGETS e tambem de acordo com o tipo de colisao
	 */
	hitTest:function(){},
	/**
	 * Me'todo inicial atribuido no evento MOUSEDOWN do objeto (ONMOUSEDOWN)
	 */
	start:function(e){
		var d = this.DD;
		
		d.move.base = this;
		d.stop.base = this;
		
		d.state.init.p=this.style.position;
		d.state.init.z=this.style.zIndex;
		d.state.init.x=this.style.left;
		d.state.init.y=this.style.top;
		
		this.style.position=d.state.next.p;
		this.style.zIndex=d.state.next.z;

		d.gap.x = (e.clientX - this.offsetLeft);
		d.gap.y = (e.clientY - this.offsetTop);

		if(d.onDrag)d.onDrag(this);
		
		compono.appendClassName("dragging",this);
		
		compono.Event.add("mousemove",d.move,document);
		compono.Event.add("mouseup",d.stop,document);
		
	},
	/**
	 * Me'todo executado durante o dragging (ONMOUSEMOVE)
	 */
	move:function(e){
		var o=arguments.callee.base;
		o.style.top=(e.clientY-o.DD.gap.y)+"px";
		o.style.left=(e.clientX-o.DD.gap.x)+"px";
		o.DD.hitTest(o);
	},
	/**
	 * Me'todo executado no drop do elemento (ONMOUSEUP)
	 */
	stop:function(e){
		var o=arguments.callee.base;
		var d=o.DD;
		compono.Event.remove("mousemove",d.move,document);
		compono.Event.remove("mouseup",d.stop,document);
		if(d.onDrop){
			if(	d.onDrop(o,!!(d.colliders.length),d.colliders) != false ){
				o.style.position = d.state.init.p;
				o.style.zIndex = d.state.init.z;
				o.style.left = d.state.init.x;
				o.style.top = d.state.init.y;
			}
		}
		compono.removeClassName("dragging",o);
	},
	/**
	 * Evento que e' executado no inicio do drag
	 */
	onDrag:function(){},
	/**
	 * Evento executado qdo ha' colisao
	 */
	onCollision:function(){},
	/**
	 * Evento executado qdo nao ha' colisao
	 */
	onNotCollision:function(){},
	/**
	 * Evento executado qdo o elemento e' solto
	 * @param {Node} No' base
	 * @param {Boolean} Indica se ha' colisao
	 * @param {Array} Colecao que carrega os elementos que participaram da colisao
	 * @return {Boolean} Se o retorno for diferente de 'false' o objeto dragado sera retornado as cordenadas iniciais. Se o retorno for 'false' o objeto permanecera' no lugar do drop.
	 */
	onDrop:function(){},
	/**
	 * @ignore
	 */
	toString:function(){return "DD"}
}








/**
 * Helper da classe de drag n' drop que carrega os testes de colisao 
 *	e tambe'm os tipos de colisao possiveis pela classe
 */
DD.collision = {
	/**
	 * Testa apenas 1 target
	 */
	oneTarget:function(a){
		this.colliders=[];
		if(arguments.callee.type(a,this.targets) && a !== this.targets){
			this.colliders[this.colliders.length]=this.targets;
			if(this.onCollision)this.onCollision(a,this.targets);
		}else{
			if(this.onNotCollision)this.onNotCollision(a,this.targets);
		}
	},
	/**
	 * Testa uma colecao de targets
	 */
	manyTargets:function(a){
		this.colliders=[];
		for(var i=0,b;b=this.targets[i];i++){
			if(arguments.callee.type(a,b) && a !== b){
				this.colliders[this.colliders.length]=b;
				if(this.onCollision)this.onCollision(a,b);
			}else{
				if(this.onNotCollision)this.onNotCollision(a,b);
			}
		}
	},
	type:{
		/**
		 * Verifica se o objeto esta 'tocando' o target
		 */
		edgeBox:function(A,B){
			return !(	A.offsetLeft + A.offsetWidth < B.offsetLeft || 
						A.offsetLeft > B.offsetLeft + B.offsetWidth || 
						A.offsetTop + A.offsetHeight < B.offsetTop || 
						A.offsetTop > B.offsetTop + B.offsetHeight		);
		},
		/**
		 * Verifica se o objeto esta totalmente dentro do target
		 */
		boundingBox:function(A,B){
			return (	A.offsetLeft > B.offsetLeft && 
						A.offsetLeft + A.offsetWidth < B.offsetLeft + B.offsetWidth && 
						A.offsetTop + A.offsetHeight < B.offsetTop + B.offsetHeight && 
						A.offsetTop > B.offsetTop		);
		}
	}
}